{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Widgets without writing widgets: interact"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The `interact` function (`ipywidgets.interact`) automatically creates user interface (UI) controls for exploring code and data interactively. It is the easiest way to get started using IPython's widgets."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "from ipywidgets import interact, interactive, fixed, interact_manual\n",
    "import ipywidgets as widgets"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Basic `interact`"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "At the most basic level, `interact` autogenerates UI controls for function arguments, and then calls the function with those arguments when you manipulate the controls interactively. To use `interact`, you need to define a function that you want to explore. Here is a function that triples its argument, `x`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def f(x):\n",
    "    return 3 * x"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "When you pass this function as the first argument to `interact` along with an integer keyword argument (`x=10`), a slider is generated and bound to the function parameter."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "interact(f, x=10);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "When you move the slider, the function is called, and the return value is printed.\n",
    "\n",
    "If you pass `True` or `False`, `interact` will generate a checkbox:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "interact(f, x=True);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If you pass a string, `interact` will generate a `Text` field."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "interact(f, x='Hi there!');"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`interact` can also be used as a decorator. This allows you to define a function and interact with it in a single shot. As this example shows, `interact` also works with functions that have multiple arguments."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "@widgets.interact(x=True, y=1.0)\n",
    "def g(x, y):\n",
    "    return (x, y)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Fixing arguments using `fixed`"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "There are times when you may want to explore a function using `interact`, but fix one or more of its arguments to specific values. This can be accomplished by wrapping values with the `fixed` function."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def h(p, q):\n",
    "    return (p, q)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "When we call `interact`, we pass `fixed(20)` for q to hold it fixed at a value of `20`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "interact(h, p=5, q=fixed(20));"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Notice that a slider is only produced for `p` as the value of `q` is fixed."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Widget abbreviations"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "When you pass an integer-valued keyword argument of `10` (`x=10`) to `interact`, it generates an integer-valued slider control with a range of `[-10, +3*10]`. In this case, `10` is an *abbreviation* for an actual slider widget:\n",
    "\n",
    "```python\n",
    "IntSlider(min=-10, max=30, step=1, value=10)\n",
    "```\n",
    "\n",
    "In fact, we can get the same result if we pass this `IntSlider` as the keyword argument for `x`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "interact(f, x=widgets.IntSlider(min=-10, max=30, step=1, value=10));"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This examples clarifies how `interact` processes its keyword arguments:\n",
    "\n",
    "1. If the keyword argument is a `Widget` instance with a `value` attribute, that widget is used. Any widget with a `value` attribute can be used, even custom ones.\n",
    "2. Otherwise, the value is treated as a *widget abbreviation* that is converted to a widget before it is used.\n",
    "\n",
    "The following table gives an overview of different widget abbreviations:\n",
    "\n",
    "<table class=\"table table-condensed table-bordered\">\n",
    "  <tr><td><strong>Keyword argument</strong></td><td><strong>Widget</strong></td></tr>  \n",
    "  <tr><td>`True` or `False`</td><td>Checkbox</td></tr>  \n",
    "  <tr><td>`'Hi there'`</td><td>Text</td></tr>\n",
    "  <tr><td>`value` or `(min,max)` or `(min,max,step)` if integers are passed</td><td>IntSlider</td></tr>\n",
    "  <tr><td>`value` or `(min,max)` or `(min,max,step)` if floats are passed</td><td>FloatSlider</td></tr>\n",
    "  <tr><td>`['orange','apple']` or `[('one', 1), ('two', 2)]`</td><td>Dropdown</td></tr>\n",
    "</table>\n",
    "Note that a dropdown is used if a list or a list of tuples is given (signifying discrete choices), and a slider is used if a tuple is given (signifying a range)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You have seen how the checkbox and text widgets work above. Here, more details about the different abbreviations for sliders and dropdowns are given.\n",
    "\n",
    "If a 2-tuple of integers is passed `(min, max)`, an integer-valued slider is produced with those minimum and maximum values (inclusively). In this case, the default step size of `1` is used."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "interact(f, x=(0, 4));"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A `FloatSlider` is generated if any of the values are floating point. The step size can be changed by passing a third element in the tuple."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "interact(f, x=(0, 10, 0.01));"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Exercise: Reverse some text\n",
    "\n",
    "Here is a function that takes text as an input and returns the text backwards."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def reverse(x):\n",
    "    return x[::-1]\n",
    "\n",
    "reverse('I am printed backwards.')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Use `interact` to make interactive controls for this function."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "# %load solutions/reverse-text.py"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "For both integer and float-valued sliders, you can pick the initial value of the widget by passing a default keyword argument to the underlying Python function. Here we set the initial value of a float slider to `5.5`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "@interact(x=(0.0, 20.0, 0.5))\n",
    "def h(x=5.5):\n",
    "    return x"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Dropdown menus are constructed by passing a list of strings. In this case, the strings are both used as the names in the dropdown menu UI and passed to the underlying Python function."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "interact(f, x=['apples','oranges']);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If you want a dropdown menu that passes non-string values to the Python function, you can pass a list of tuples of the form `('label', value)`. The first items are the names in the dropdown menu UI and the second items are values that are the arguments passed to the underlying Python function."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "interact(f, x=[('one', 10), ('two', 20)]);"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "tags": []
   },
   "source": [
    "## Basic interactive plot\n",
    "\n",
    "\n",
    "The function below plots a straight line whose slope and intercept are given by its arguments."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "\n",
    "def f(m, b):\n",
    "    fig = plt.figure()\n",
    "    plt.clf()\n",
    "    plt.grid()\n",
    "    x = np.linspace(-10, 10, num=1000)\n",
    "    plt.plot(x, m * x + b)\n",
    "    plt.ylim(-5, 5)\n",
    "    plt.show()\n",
    "\n",
    "interact(f, m=(-2.0, 2.0), b=(-3, 3, 0.5))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Fully interactive plot\n",
    "\n",
    "While the above example works, it has some drawbacks:\n",
    "1. It is inefficient to re-run all the plotting code (the whole plot gets re-created every time -- to see this, run the code above again after running the cell below and see how the figure numbers get incremented)\n",
    "2. No zooming or panning\n",
    "3. Screen can jump when moving the sliders\n",
    "\n",
    "A better solution is to use the [ipympl](https://matplotlib.org/ipympl/) Matplotlib backend. You can activate this with the line magic: `%matplotlib ipympl`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "# Activate the widget based backend.\n",
    "%matplotlib ipympl\n",
    "\n",
    "x = np.linspace(-10, 10, num=1000)\n",
    "fig, ax = plt.subplots()\n",
    "ax.grid()\n",
    "ax.set_ylim(-5, 5)\n",
    "# Initialize a plot object with y = x. We'll be modifying y below.\n",
    "# This returns a list of `.Line2D` representing the plotted data. We grab the first one -- we only have 1 series.\n",
    "line = ax.plot(x, x)[0]\n",
    "\n",
    "@interact(m=(-2.0, 2.0), b=(-3, 3, 0.5))\n",
    "def update_line(m=1, b=0.5):\n",
    "    line.set_ydata(m * x + b)\n",
    "    # Request a widget redraw.\n",
    "    fig.canvas.draw_idle()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## mpl-interactions\n",
    "\n",
    "The [mpl-interactions](https://mpl-interactions.readthedocs.io/en/stable/) library can automate the updating of Matplotlib plots for you. Non-matplotlib keyword arguments passed to functions like `plot` will be interpreted to sliders and widget controls, similarly to `interact`, and automatically connected with the matplotlib plots."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "from mpl_interactions import ipyplot as iplt\n",
    "\n",
    "fig, ax = plt.subplots()\n",
    "ax.grid()\n",
    "ax.set_ylim(-5,5)\n",
    "\n",
    "# Define function in a way you can re-use in calculations\n",
    "def f(x, m, b):\n",
    "    return m * x + b\n",
    "    \n",
    "ctrls = iplt.plot(x, f, m=(-2,2), b=(-3, 3, 10))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Exercise: Make a plot\n",
    "\n",
    "Here is an example of making a plot of $f(x) = \\sin(k x - p)$.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "x = np.linspace(0, 4 * np.pi, 100)\n",
    "k = 1\n",
    "p = np.pi\n",
    "\n",
    "def f(x, k, p):\n",
    "    return np.sin(k*x -p)\n",
    "fig, ax = plt.subplots()\n",
    "ax.grid()\n",
    "ax.plot(x, f(x, k, p))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Copy the above function definition and make it interactive using `interact` or `mpl-interactions`, so that there are sliders for the parameters $k$ and $p$, where $0.5\\leq k \\leq 2$ and $0 \\leq p \\leq 2\\pi$ (hint: use `np.pi` for $\\pi$)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "# %load solutions/plot-function-interact.py"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "# %load solutions/plot-function-mpl-inter.py"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# For more information \n",
    "\n",
    "See the notebook below ('OPTIONAL More About Interact') for more information about other ways of generating interactive controls for functions and for details about how to control when sliders are updated.\n",
    "\n",
    "For more extended examples of `interact` and `interactive`, see [the example in the ipywidgets source repository](https://github.com/jupyter-widgets/ipywidgets/blob/master/docs/source/examples/Index.ipynb)."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "widgets-tutorial",
   "language": "python",
   "name": "widgets-tutorial"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.13"
  },
  "widgets": {
   "application/vnd.jupyter.widget-state+json": {
    "state": {},
    "version_major": 2,
    "version_minor": 0
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
